package com.example.sentinel;

import com.alibaba.csp.sentinel.AsyncEntry;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

public class AsyncEntryDemo {

    public static void main(String[] args) throws InterruptedException {
        initFlowRule();
        AsyncEntryDemo service = new AsyncEntryDemo();

        ContextUtil.enter("async-context", "originA");
        Entry entry = null;
        try {
            entry = SphU.entry("test-top");
            System.out.println("Do something ...");
            service.doAsyncThenSync();
        } catch (BlockException ex) {
            ex.printStackTrace();
        } finally {
            if (Objects.nonNull(entry)) {
                entry.exit();
            }
            ContextUtil.exit();
        }
        TimeUnit.SECONDS.sleep(20);
    }
    private static void initFlowRule() {
        FlowRule rule1 = new FlowRule()
                .setRefResource("test-another-sync-in-async")
                .setLimitApp("originB")
                .as(FlowRule.class)
                .setCount(4)
                .setGrade(RuleConstant.FLOW_GRADE_QPS);

        FlowRule rule2 = new FlowRule()
                .setResource("test-another-async")
                .setLimitApp("default")
                .as(FlowRule.class)
                .setCount(5)
                .setGrade(RuleConstant.FLOW_GRADE_QPS);

        List<FlowRule> ruleList = Arrays.asList(rule1, rule2);
        FlowRuleManager.loadRules(ruleList);
    }

    private void doAsyncThenSync() {
        try {
            final AsyncEntry entry = SphU.asyncEntry("test-async");
            this.invoke("abc", resp ->{
                ContextUtil.runOnContext(entry.getAsyncContext(), () -> {
                    try {
                        for (int i = 0; i < 7; i++) {
                            anotherAsync();
                        }
                        System.out.println(resp);
                        fetchSyncInAsync();
                    } finally {
                        entry.exit();
                    }

                });
            });
            fetchSync();
        } catch (BlockException ex) {
            ex.printStackTrace();
        }
    }

    private void invoke(String arg, Consumer<String> handler) {
        CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                String resp = arg + ": " + System.currentTimeMillis();
                handler.accept(resp);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        });
    }


    private void anotherAsync() {
        try {
            final AsyncEntry entry = SphU.asyncEntry("test-another-async");
            CompletableFuture.runAsync(() -> {
                try {
                    TimeUnit.SECONDS.sleep(2);
                    anotherSyncInAsync();
                    System.out.println("Async result: 666");
                } catch (InterruptedException ex) {
                    // Ignore
                } finally {
                    entry.exit();
                }
            });
        } catch (BlockException ex) {
            ex.printStackTrace();
        }
    }

    private void anotherSyncInAsync() {
        Entry entry = null;
        try {
            entry = SphU.entry("test-another-sync-in-async");
        } catch (BlockException ex) {
            ex.printStackTrace();
        } finally {
            if (Objects.nonNull(entry)) {
                entry.exit();
            }
        }
    }

    private void fetchSyncInAsync() {
        Entry entry = null;
        try {
            entry = SphU.entry("test-sync-in-async");
        } catch (BlockException ex) {
            ex.printStackTrace();
        } finally {
            if (Objects.nonNull(entry)) {
                entry.exit();
            }
        }
    }

    private void fetchSync() {
        Entry entry = null;
        try {
            entry = SphU.entry("test-sync");
        } catch (BlockException ex) {
            ex.printStackTrace();
        } finally {
            if (Objects.nonNull(entry)) {
                entry.exit();
            }
        }
    }
}
